package org.zhuzx.util;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 运行时信息工具类
 * @author	zhuzx	2017年5月22日
 */
@SuppressWarnings("unchecked")
public class RttiUtil {

	/**
	 * 获取由该类的Constructor对象组成的Map，以最简方法签名为键。
	 */
	public static <T> Map<String,Constructor<T>> getDeclaredConstructors(Class<T> clazz) {
		String cacheKey = RttiUtil.class.getSimpleName() + ".getDeclaredConstructors-" + clazz.getName();
		return UnifiedConstants.getRuntimeConstant(cacheKey, () -> {
			Constructor<T>[] constructors = (Constructor<T>[]) clazz.getDeclaredConstructors();
			String packageNamePrefix = clazz.getPackage().getName() + ".";
			Map<String,Constructor<T>> map = new LinkedHashMap<>(constructors.length);
			for (Constructor<T> constructor : constructors) {
				constructor.setAccessible(true);
				//toString()方法返回示例：public org.zhuzx.util.GenericResult(int,java.lang.String)
				String name = constructor.toString();
				//只取类名开始的后半部分
				name = name.substring(name.indexOf(packageNamePrefix) + packageNamePrefix.length());
				if (name.contains(".")) {//去掉参数类名中的包路径
					name = name.replaceAll("(\\w+\\.)+", "");
				}
				map.put(name, constructor);
			}
			return map;
		});
	}

	/**
	 * 获取由该类的Method对象组成的Map，以最简方法签名为键。
	 */
	public static Map<String,Method> getDeclaredMethods(Class<?> clazz) {
		String cacheKey = RttiUtil.class.getSimpleName() + ".getDeclaredMethods-" + clazz.getName();
		return UnifiedConstants.getRuntimeConstant(cacheKey, () -> {
			Method[] methods = clazz.getDeclaredMethods();
			String classNamePrefix = clazz.getName() + ".";
			Map<String,Method> map = new LinkedHashMap<>(methods.length);
			for (Method method : methods) {
				method.setAccessible(true);
				//toString返回示例：handle=public org.zhuzx.util.AbstractResult org.zhuzx.util.CommonExceptionHandler.handle(org.zhuzx.util.BizException)
				String allStr = method.toString();
				//只取方法名开始的后半部分
				String fullName = classNamePrefix + method.getName();
				//括号包围的参数部分
				String paramStr = allStr.substring(allStr.indexOf(fullName) + fullName.length());
				if (paramStr.contains(".")) {//去掉参数类名中的包路径
					paramStr = paramStr.replaceAll("(\\w+\\.)+", "");
				}
				map.put(method.getName() + paramStr, method);
			}
			return map;
		});
	}

	/**
	 * 获取由该类声明的字段的Field对象组成的Map，以属性名为键。<br>
	 * 包含私有属性。
	 */
	public static Map<String,Field> getDeclaredFields(Class<?> clazz) {
		String cacheKey = RttiUtil.class.getSimpleName() + ".getDeclaredFields-" + clazz.getName();
		return UnifiedConstants.getRuntimeConstant(cacheKey, () -> {
			Field[] fields = clazz.getDeclaredFields();
			Map<String,Field> map = new LinkedHashMap<>(fields.length);
			for (Field field : fields) {
				field.setAccessible(true);
				map.put(field.getName(), field);
			}
			return map;
		});
	}

	/**
	 * 获取由该类全部字段的Field对象组成的Map，以属性名为键。<br>
	 * 包括本身声明的和继承的，若父类中有同名属性则以子类的为准。<br>
	 * 包含私有属性（本身的和父类的）。
	 */
	public static Map<String,Field> getAllFields(Class<?> clazz) {
		String cacheKey = RttiUtil.class.getSimpleName() + ".getAllFields-" + clazz.getName();
		return UnifiedConstants.getRuntimeConstant(cacheKey, () -> {
			Map<String,Field> map = new LinkedHashMap<>();
			Class<?> curClazz = clazz;
			while (curClazz != null && curClazz != Object.class) {
				for (Field field : curClazz.getDeclaredFields()) {
					//在子类里面没有的属性才会加入这个键，以子类为准（覆盖父类的）。
					if (!map.containsKey(field.getName())) {
						field.setAccessible(true);
						map.put(field.getName(), field);
					}
				}
				curClazz = curClazz.getSuperclass();
			}
			return map;
		});
	}

	/**
	 * 用反射获取对象中的属性值，注意：这个对象必须拥有该属性，否则抛错。<br>
	 * 包括本身声明的和继承的，若父类中有同名属性则以子类的为准。<br>
	 * 包含私有属性（本身的和父类的）。
	 */
	public static <T> T getFieldValue(Object obj, String fieldName) {
		Field field = getField(obj, fieldName);
		try {
			return (T) field.get(obj);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("程序逻辑错误", e);
		}
	}

	/** 获取属性名对应的Field对象 */
	private static Field getField(Object obj, String fieldName) {
		Class<?> clazz = obj.getClass();
		Field field = getAllFields(clazz).get(fieldName);
		if (field == null) {
			throw new IllegalArgumentException(clazz.getSimpleName() + "中不存在属性" + fieldName);
		}
		return field;
	}

	/**
	 * 断言target类型的值能够分配（赋值）给expected类型
	 */
	public static void assertIsAssignable(Class<?> target, Class<?> expected) {
		String targetName = target.getSimpleName();
		String expectedName = expected.getSimpleName();
		if (targetName.equals(expectedName)) {
			targetName = target.getName();
			expectedName = expected.getName();
		}
		Assert.mustTrue(expected.isAssignableFrom(target),
				"类型不匹配：" + targetName + "无法分配给类型" + expectedName);
	}

	/**
	 * 通过反射设置对象中的属性值，注意：这个对象必须拥有该属性并且类型匹配（能直接赋值），否则抛错。<br>
	 * 包括本身声明的和继承的，若父类中有同名属性则以子类的为准。<br>
	 * 包含私有属性（本身的和父类的）。
	 */
	public static void setFieldValue(Object obj, String fieldName, Object value) {
		Field field = getField(obj, fieldName);
		if (value != null) {
			assertIsAssignable(value.getClass(), field.getType());
		}
		try {
			field.set(obj, value);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("程序逻辑错误", e);
		}
	}

	/**
	 * 获取服务器解码请求url中附带的queryString所用的字符集
	 */
	@Deprecated//SpringBoot时代已失去价值
	static Charset getDecodeCharsetOfQueryString(HttpServletRequest request) {
		Charset charset = (Charset) UnifiedConstants.getRuntimeConstant("DecodeCharsetOfQueryString");
		if (charset == null) {//@Deprecated//分布式部署时可能会存在多个tomcat而编码设置不统一的情况
			if (request.getClass().getName().equals("org.apache.catalina.connector.RequestFacade")) {
				String[] hierarchyFields = {"request", "coyoteRequest", "queryMB", "byteC", "charset"};
				Object obj = request;
				try {
					for (String field : hierarchyFields) {
						obj = getFieldValue(obj, field);
					}
					charset = (Charset) obj;
				} catch (Exception e) {
					e.printStackTrace();
					charset = StandardCharsets.UTF_8;
				}
			} else {
				charset = StandardCharsets.UTF_8;//@Deprecated//暂时只支持Tomcat，若是其它容器则返回UTF-8。
			}
			UnifiedConstants.setRuntimeConstant("DecodeCharsetOfQueryString", charset);
		}
		return charset;
	}

	/**
	 * 将该对象的所有属性输出为属性名：属性值的形式，然后以\n连接成为一个大字符串。<br>
	 * 具备额外的美化功能——将属性名垂直对齐，本方法用于调试或者其它需要鲜明展示的场景。
	 */
	public static String objectToString(Object obj) {
		Field[] fields = getDeclaredFields(obj.getClass()).values().toArray(new Field[0]);
		String[][] pairs = new String[fields.length][2];
		int maxLength = 0;
		for (int i = 0; i < fields.length; i++) {
			Field field = fields[i];
			Object value;
			try {
				value = field.get(obj);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			String fieldName = field.getName();
			pairs[i][0] = fieldName;
			pairs[i][1] = GeneralUtil.toString(value);
			if (fieldName.length() > maxLength) {
				maxLength = fieldName.length();
			}
		}
		StringBuilder sb = new StringBuilder();
		for (String[] pair : pairs) {
			int differ = maxLength - pair[0].length();
			if (differ > 0) {
				sb.append(MicroUtil.createRepeatedCharSequence(' ', differ));
			}
			sb.append(pair[0]).append("：").append(pair[1]).append("\n");
		}
		if (sb.length() > 0) {
			sb.deleteCharAt(sb.length() - 1);
		}
		return sb.toString();
	}

	/**
	 * 获取clazz类中存在的所有annotationClass类型的注解，包括类、构造器、方法、属性上的，支持重复的注解。<br>
	 * 仅限于这个类本身声明的而不包括继承的。
	 */
	@SuppressWarnings("unused")
	private static <A extends Annotation> List<A> getDeclaredAnnotations(Class<?> clazz, Class<A> annotationClass) {
		List<A> list = new ArrayList<>(Arrays.asList(clazz.getAnnotationsByType(annotationClass)));
		List<AccessibleObject> aoList = new ArrayList<>();
		aoList.addAll(getDeclaredConstructors(clazz).values());
		aoList.addAll(getDeclaredMethods(clazz).values());
		aoList.addAll(getDeclaredFields(clazz).values());
		aoList.forEach(ao -> list.addAll(Arrays.asList(ao.getAnnotationsByType(annotationClass))));
		return list;
	}

	/**
	 * 获取clazz类头的全部注解，包括继承的父类的类头注解。<br>
	 * 子类的注解在子List的前面。不支持重复的注解，若标注了多个则只取第一个，父类上标注的同类型注解会被忽略。
	 * @return 注解类型为键，这个类型下的注解对象为值。
	 */
	public static Map<Class<? extends Annotation>, Annotation> getAllAnnotationsOnClass(Class<?> clazz) {
		String cacheKey = RttiUtil.class.getSimpleName() + ".getAllAnnotationsOnClass-" + clazz.getName();
		return UnifiedConstants.getRuntimeConstant(cacheKey, () -> {
			List<Annotation> list = new ArrayList<>();
			Class<?> curClazz = clazz;
			while (curClazz != null && curClazz != Object.class) {
				list.addAll(Arrays.asList(curClazz.getAnnotations()));
				curClazz = curClazz.getSuperclass();
			}
			return list.stream().collect(Collectors.toMap(
					Annotation::annotationType, item -> item, LambdaConstants.takeTheFirstMerger(), LinkedHashMap::new));
		});
	}

	public static <A> A getAnnotationOnClassAndAssert(Class<?> clazz, Class<A> annotationClass) {
		A annotation = (A) RttiUtil.getAllAnnotationsOnClass(clazz).get(annotationClass);
		Assert.notNull(annotation, clazz.getSimpleName() + "类没有标注"
				+ annotationClass.getSimpleName() + "注解");
		return annotation;
	}

}